package com.pai4j.common.util;
import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.pai4j.common.enums.LocalCacheKey;
import com.pai4j.domain.vo.response.IpLocationResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;


import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

/**
 *  ip地址解析
 *
 * @author: CYM-pai
 * @date: 2025/04/17 19:51
 **/
@Slf4j
public class IPAddressUtil {
    
    
    private static String localIP;

    /**
     * IP 归属地解析API（免费）
     */
    private static final String IP_ADDRESS_API = "https://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";

    // IP地址本地缓存
    private static final Cache<String, IpLocationResponseVO> IP_LOCAL_CACHE;

    // 初始化本地缓存
    static {
        IP_LOCAL_CACHE = CacheBuilder.newBuilder()
                .softValues()
                .maximumSize(500L)
                .expireAfterWrite(600L, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 根据IP地址获取其归属地信息
     *
     * @param ip IP地址字符串
     * @return IpLocationResponseVO对象，包含IP地址的归属地信息
     */
    public static IpLocationResponseVO location(String ip) {

        // 检查IP地址是否为空
        if (StringUtils.isBlank(ip)) {
            return null;
        }

        // 构建本地缓存键
        String localCacheKey = LocalCacheKey.IP_LOCATION.name().concat("_").concat(ip);
        // 从本地缓存中获取归属地信息
        IpLocationResponseVO localCacheVal = IP_LOCAL_CACHE.getIfPresent(localCacheKey);
        if (localCacheVal != null) {
            // 如果本地缓存中存在，则直接返回
            log.info("{} 规则归属地解析成功 - FROM LOCAL CACHE:{}", ip, JsonUtil.toJsonString(localCacheVal));
            return localCacheVal;
        }
        // 通过API查询
        IpLocationResponseVO ipLocationResponseVO = locationFromApi(ip);
        // 加入本地缓存
        IP_LOCAL_CACHE.put(localCacheKey, ipLocationResponseVO);
        log.info("{} 规则归属地解析成功 - FROM API:{}", ip, JsonUtil.toJsonString(ipLocationResponseVO));
        return ipLocationResponseVO;
    }

    /**
     * 通过API获取IP地址的归属地信息
     *
     * @param ip IP地址字符串
     * @return IpLocationResponseVO对象，包含IP地址的归属地信息
     */
    private static IpLocationResponseVO locationFromApi(String ip) {
        IpLocationResponseVO ipLocationResponseVO = IpLocationResponseVO.builder().build();
        // 根据IP地址生成查询URL
        String url = String.format(IP_ADDRESS_API, ip);
        try {
            // 发送请求获取归属地信息
            String response = HttpClientUtil.getObjectFromUrl(url, String.class);
            if (StringUtils.isBlank(response)) {
                return ipLocationResponseVO;
            }
            // 解析响应JSON
            JSONObject responseJson = JSONObject.parseObject(response);
            return IpLocationResponseVO.builder()
                    .country(responseJson.getString("country"))
                    .province(responseJson.getString("pro"))
                    .city(responseJson.getString("city"))
                    .district(responseJson.getString("region"))
                    .build();
        } catch (Exception e) {
            // 记录错误日志
            log.error("IP LOCATION analysis error. ip:{}", ip, e);
        }
        return ipLocationResponseVO;
    }
    
    
    /**
     * 获取本机的IPv4地址。如果获取到的是127.0.0.1，则使用备用方法获取外部可访问的IPv4地址。
     *
     * @return 本机的IPv4地址
     */
    public static String getLocalIPv4Address() {
        
        if (localIP != null) {
            return localIP;
        }
        // 尝试使用 InetAddress.getLocalHost() 获取本机IP地址
        String ip = getLocalIp();
        
        // 检查是否为环回地址
        if (ip == null || ip.equals("127.0.0.1")) {
            log.warn("LocalIpUtil. Failed to fetch local IP address, using fallback method");
            ip = getExternalIPv4Address();
        }
        localIP = ip;
        return ip;
    }
    
    
    /**
     * 获取本机ip
     * 优点
     * 简单易用: 代码简单，使用单一方法即可获取IP地址。
     * 快速: 直接获取本地IP地址，不需要遍历所有网络接口，效率较高。
     * 广泛支持: 适用于大多数简单的网络配置。
     * 缺点
     * 可能返回环回地址: 在某些情况下（例如本地主机名配置不正确），可能返回 127.0.0.1（环回地址），这在多网卡或复杂网络环境中可能不适用。
     * 不适用多网卡环境: 在多网卡或多IP环境中，不能选择特定的外部可访问地址。
     */
    private static String getLocalIp() {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            return localHost.getHostAddress();
        } catch (Exception e) {
            log.error("LocalIpUtil. Failed to fetch local IP address", e);
            return null;
        }
    }
    
    
    /**
     * 获取本地 I PV 4 地址
     * 优点
     * 高可靠性: 可遍历所有网络接口，获取外部可访问的IPv4地址，避免返回环回地址。
     * 适用复杂网络环境: 适用于多网卡、多IP地址的复杂网络环境。
     * 灵活性高: 可以根据需求过滤和选择特定的网络接口和IP地址。
     * 缺点
     * 复杂性高: 代码相对复杂，需要处理多种情况。
     * 性能较低: 需要遍历所有网络接口，效率相对较低。
     * 依赖网络接口状态: 依赖于网络接口的状态和配置，如果网络接口未激活或配置不当，可能无法获取正确的IP地址。
     */
    public static String getExternalIPv4Address() {
        // 获取所有网络接口的枚举。
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = networkInterfaces.nextElement();
                //isLoopback() 和 isUp(): 用于过滤掉回环接口和未激活的接口。
                if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                    continue;
                }
                // 获取给定网络接口的所有IP地址
                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                while (inetAddresses.hasMoreElements()) {
                    InetAddress inetAddress = inetAddresses.nextElement();
                    if (inetAddress instanceof java.net.Inet4Address && !inetAddress.isLoopbackAddress()) {
                        return inetAddress.getHostAddress();
                    }
                }
            }
            return null;
        } catch (Exception e) {
            log.error("LocalIpUtil. Failed to fetch local IPv4 address", e);
        }
        return null;
    }
    
    
}

